/**
 *******************************************************************************************************************************************
 * @file        mem_sdram.c
 * @brief       管理片内 SDRAM 存储空间
 * @details     Apply to On-Chip SDRAM
 * @since       Change Logs:
 * Date         Author       Notes
 * 2023-02-23   lzh          the first version
 * @remark      提供两类不同的实现方式 : LVGL内建 与 自定义实现
 *******************************************************************************************************************************************
 * @attention
 *
 * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS WITH CODING INFORMATION
 * REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME. AS A RESULT, SYNWIT SHALL NOT BE HELD LIABLE
 * FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
 * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONN-
 * -ECTION WITH THEIR PRODUCTS.
 *
 * @copyright   2012 Synwit Technology
 *******************************************************************************************************************************************
 */
#include <string.h>
#include "mem_sdram.h"

#include "version.h"
#include "lv_conf.h"

/* SDRAM 内存管理 :
 * SDRAMM_BASE =>
 *
 * 功能保留区 : 静态编译阶段确定, 用于实现特定功能, 如不需要某些功能可根据需要删减/复用
 * {
 *      [LCD_W * LCD_H * 2]     : LVGL 绘图区, 充当显存 (详见 lv_porting/lv_port_disp.c)
 *      [LCD_W * LCD_H]         : (512KB)  JPEG 解码缓冲区, 用于全局 JPEG 解码图片(详见 bsp/dev_jpeg.c)
 *      [LV_MEM_SIZE]           : LVGL 控件对象所需内存 (详见 lv_conf.h line-91)
 *      [UserBoot 2 Cache Buff] : (4KB * 2)  UserBoot 读写双缓存
 * }
 *
 * [Other Memory] :  由本模块进行管理
 *
 * <= [SDRAMM_BASE + SDRAMM_SIZE]
 */
/*******************************************************************************************************************************************
 * Private Define
 *******************************************************************************************************************************************/
/** 按照 X Bytes 对齐, X 必须能被 2 整除(取值 1 例外) */
#define ALIGN_X					(4)

/** 返回对齐 size 后的值(向上对齐) */
#define MEM_ALIGN_UP(size)		ALIGN_UP((size), (ALIGN_X))

/*
 * 在 "lv_conf.h" 中 修改 #define LV_MEM_SIZE 取值可变更切换此处内存管理方式
 * 0     - LVGL 库内建的内存管理
 * 1     - 内存池 + 堆实现(优点:地址连续,不会产生内存碎片, 分配速度快;   缺点:当位于高地址的内存块被分配获取后, 不能释放低地址的内存块)
 * other - 可以自己实现一套内存管理机制, 保持接口一致即可
 */
#if (LV_MEM_SIZE != (SDRAMM_SIZE - (LCD_HDOT * LCD_VDOT * LCD_FMT * 2) - 512 * 1024 - 4096 * 2))

/** 内存块标识符 */
#define MAGIC_SWM34S        (0xABCDDCBA)

/*******************************************************************************************************************************************
 * Private Typedef
 *******************************************************************************************************************************************/
typedef struct
{
    size_t magic; /**< 内存块标识符 */
    size_t size;  /**< 内存块已分配的大小 / Bytes(已包含sizeof(mem_header_t)) */
    void *data;   /**< 内存块首地址 */
} mem_header_t;   /**< 每块内存被分配确定后的头部 */

typedef struct
{
    const void *addr;  /**< 内存池起始地址 */
    const size_t size; /**< 内存池总大小 / Bytes */

//    size_t block_size;        /**< 内存块大小 / Bytes */
//    uint8_t *block_list;      /**< 内存块列表 */
//    size_t block_total_count; /**< 内存池数据区域中能够容纳的最大内存块数 */
//    size_t block_free_count;  /**< 内存池中空闲的内存块数 */

    uint8_t thread_mutex; /**< 线程互斥锁(0:空闲 1:占用) */
    size_t ptr;           /**< 内存读写指针, 始终指向下一块空闲内存 (地址向上增长) */

} mm_t; /**< 内存管理结构体 */

/*******************************************************************************************************************************************
 * Private Variable
 *******************************************************************************************************************************************/
/** 内存池 */
static uint8_t Memory_Pool[(SDRAMM_SIZE - (LCD_HDOT * LCD_VDOT * LCD_FMT * 2) - 512 * 1024 - LV_MEM_SIZE - 4096 * 2)]
    __attribute__((section(".SDRAM1"), aligned(4)));

/** 内存句柄 */
static volatile mm_t Memory_Handle = {
    .addr = (void *)Memory_Pool,
    .size = sizeof(Memory_Pool) / sizeof(Memory_Pool[0]),
    .thread_mutex = 0,
    .ptr = 0,
};

#ifdef HOOK_ENABLE
typedef void (*sdram_malloc_hook_t)(void *ptr, size_t size);
typedef void (*sdram_free_hook_t)(void *ptr);

static sdram_malloc_hook_t sdram_malloc_hook;
static sdram_free_hook_t sdram_free_hook;

void sdram_malloc_set_hook(sdram_malloc_hook_t hook)
{
	sdram_malloc_hook = hook;
}

void sdram_free_set_hook(sdram_free_hook_t hook)
{
	sdram_free_hook = hook;
}
#endif

/*******************************************************************************************************************************************
 * Public Variable
 *******************************************************************************************************************************************/
/**
 * @brief       申请一块内存
 * @param [in]  size    : 期望分配的内存块大小
 * @retval      !NULL   : 分配的内存块首地址
 * @retval      NULL    : error
 */
void *sdram_malloc(size_t size)
{
	size_t base_addr = (size_t)(Memory_Handle.addr);
    size_t max_size = Memory_Handle.size;
    size_t ptr = Memory_Handle.ptr;
	size_t ptr_size = size;
	
	if (ptr >= max_size - size || size == 0) 
		return NULL; /* assert */
	
	#define TWO_ADD(x, y)		((x) + (y))

    /* 地址对齐 */
    size_t offset_pad = MEM_ALIGN_UP( TWO_ADD(ptr, sizeof(mem_header_t)) ) - ptr;

    /* 计算总占用 */
    ptr_size += offset_pad;

    /* 剩余容量判定 */
    if (ptr >= max_size - ptr_size)
        return NULL;

	/* 写入前加锁以保护临界段, 避免同时访问竞争产生不可预知的内存错误 */
	if (Memory_Handle.thread_mutex != 0)
		return NULL;
	Memory_Handle.thread_mutex = 1;
	
    /* 填充内存块 */
	mem_header_t *header = (mem_header_t *)(TWO_ADD(base_addr, ptr + offset_pad) - sizeof(mem_header_t));
	header->magic = MAGIC_SWM34S;
	header->size = ptr_size;
	header->data = (void *)(TWO_ADD(base_addr, ptr + offset_pad));
	
    Memory_Handle.ptr += ptr_size;
    
#ifdef HOOK_ENABLE
    if (sdram_malloc_hook)
        sdram_malloc_hook((void *)ptr, ptr_size);
#endif

    /* 释放锁 */
    Memory_Handle.thread_mutex = 0;
    return header->data;
	
	#undef TWO_ADD
}

/**
 * @brief       释放一块内存
 * @param [in]  *ptr  : 分配的内存块首地址
 * @retval      0     : success
 * @retval      other : error code
 */
uint8_t sdram_free(void *ptr)
{
    mem_header_t *header = (mem_header_t *)((size_t)ptr - sizeof(mem_header_t));

    if (header == NULL \
		|| ( (size_t)header <= (size_t)(Memory_Handle.addr) ) \
		|| ( (size_t)header >= Memory_Handle.size + (size_t)(Memory_Handle.addr) - sizeof(mem_header_t) ) \
		|| header->magic != MAGIC_SWM34S \
	    || header->size > Memory_Handle.ptr \
		)
		{
			return 1; /* assert */
		}

	/* 写入前加锁以保护临界段, 避免同时访问竞争产生不可预知的内存错误 */
	if (Memory_Handle.thread_mutex != 0)
		return 2;
	Memory_Handle.thread_mutex = 1;

    Memory_Handle.ptr -= header->size;
    
    /* 立即擦空(可选), 一般标记为脏值即可, 无须擦空而消耗时间 
	size_t addr = (size_t)header;
	size_t size = (size_t)(header->size);
	memset((void *)addr, 0, size);
	*/
	
	/* 清除内存头, 标记为脏块 */
	//memset(free_head, 0, sizeof(mem_header_t));
	header->magic = 0;
	header->size = ~0;
	header->data = NULL;
    
#ifdef HOOK_ENABLE
    if (sdram_free_hook)
        sdram_free_hook((void *)ptr);
#endif
    
    /* 释放锁 */
    Memory_Handle.thread_mutex = 0;
    return 0;
}

/**
 * @brief   获取空闲可用内存大小
 * @param   /
 * @retval  内存池剩余可用内存大小
 */
size_t sdram_get_idle_size(void)
{
    return (Memory_Handle.size - Memory_Handle.ptr);
}

#else

#include "lvgl.h"

/**
 * @brief       申请一块内存
 * @param [in]  size    : 期望分配的内存块大小
 * @retval      !NULL : 分配的内存块首地址
 * @retval      NULL    : error
 */
void *sdram_malloc(size_t size)
{
	size = MEM_ALIGN_UP(size);
    /* lvgl 暂未提供内存对齐选项, 需要等到 V9 版本才有 */

#if 1 // malloc
    return lv_mem_alloc(size);

#else // calloc
    void *ptr = lv_mem_alloc(size);
    memset(ptr, 0, size);
    return ptr;

#endif
}

/**
 * @brief       释放一块内存
 * @param [in]  *ptr  : 分配的内存块首地址
 * @retval      0     : success
 * @retval      other : error code
 */
uint8_t sdram_free(void *ptr)
{
    lv_mem_free(ptr);
    return 0;
}

/**
 * @brief   获取空闲可用内存大小
 * @param   /
 * @retval  内存池剩余可用内存大小
 */
size_t sdram_get_idle_size(void)
{
    lv_mem_monitor_t mem_monitor;
    lv_mem_monitor(&mem_monitor);
    return mem_monitor.free_size;
}

#endif

/**
 * @brief   测试本模块内存管理
 * @param   /
 * @retval  0     : success
 * @retval  other : error code
 */
uint8_t mm_test(void)
{
    void *ptr[100];
    size_t free_size = 0;
    uint8_t res = 0;
	for (uint32_t i = 0; i < sizeof(ptr) / sizeof(ptr[0]); ++i)
    {
        ptr[i] = sdram_malloc(i * 1024);
        free_size = sdram_get_idle_size();
        if (ptr[i] == NULL)
        {
            res = 3;
            break;
        }
        res = sdram_free(ptr[i]);
        free_size = sdram_get_idle_size();
        if (res != 0)
            break;
    }
    return res;
}
